package org.xtremeobjects.domain.aspects.states;


import java.util.ArrayList;
import java.util.List;

import org.xtremeobjects.anotations.Help;
import org.xtremeobjects.anotations.state.Event;
import org.xtremeobjects.anotations.state.InState;
import org.xtremeobjects.anotations.state.StateField;
import org.xtremeobjects.anotations.state.OnStateEntry;
import org.xtremeobjects.anotations.state.StateMachine;
import org.xtremeobjects.anotations.state.SuperState;
import org.xtremeobjects.anotations.state.Transition;



/**
 * This is an example from the state machine depicted in: 
 * http://www.agilemodeling.com/artifacts/stateMachineDiagram.htm
 * @author   Juan
 */
@StateMachine(initialState="Proposed")
public class Seminar {
	private String name;
	private Object when;
	private Object where;
	private List students = new ArrayList();
	private List waitingList = new ArrayList();
	private int totalSeats;

	public enum SeminarState {
		Enrollement,
		@SuperState("Enrollement")
		Proposed,
		@SuperState("Enrollement")
		Scheduled,
		@SuperState("Enrollement")
		OpenForEnrollement,
		@SuperState("Enrollement")
		Full,
		@SuperState("Enrollement")
		ClosedToEnrollement,
		BeingTaught,
		FinalExams,
		End
	};

	@StateField
	private SeminarState state;
	
	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public int getTotalSeats() {
		return totalSeats;
	}

	public boolean isSeatAvailable() {
		return ((totalSeats-students.size())>0);
	}
	
	public int getSeminarSize() {
		return students.size();
	}
	
	public Object getWhen() {
		return when;
	}

	public Object getWhere() {
		return where;
	}

	public SeminarState getState() {
		return state;
	}

	public Seminar(String name) {
		this.setName(name);
	}
	
	@Event(@Transition(from="Proposed", to="Scheduled"))
	public void scheduled( Object when, Object place, int totalSeats ) {
		this.when=when;
		this.where=place;
		this.totalSeats=totalSeats;
	}
	
	@Event(@Transition(from="Scheduled", to="OpenForEnrollement"))
	public void open( ) { 
	}
	
	@Event(@Transition(from={"Enrollement", "BeingTaught", "FinalExams"}, to="End"))
	public void cancelled( ) {
	}

	@Event({
		@Transition(from={"OpenForEnrollement"}, to="OpenForEnrollement", 
				guard="self.isSeatAvailable()", 
				guardDescr="seat available"),
		@Transition(from={"OpenForEnrollement"}, to="Full", 
				guard="!self.isSeatAvailable()", 
				guardDescr="no seat available"), /**/
		@Transition(from="Full", to="Full")
	})
	public void studentEnrolled(Object student) {
		switch(state) {
		case OpenForEnrollement:
			addStudent(student);
			break;
		case Full:
			addToWaitingList(student);
			considerSplit();
			break;
		default:
			throw new RuntimeException("Invalid State");
		}
	}

	private void considerSplit() {
		if(waitingList.size()>5)
			seminarSplit();
	}
	
	@Event(@Transition(from="Full", to="OpenForEnrollement"))
	private void seminarSplit() {
		System.out.println("Seminar splited");
		waitingList.clear();
		students.clear();
	}

	@Event({
		@Transition(from="Full", to="Full"),
		@Transition(from="BeingTaught", to="End", guard="self.getSeminarSize()==0"),
		@Transition(from="BeingTaught", to="BeingTaught", guard="self.getSeminarSize()>0")
	})
	public void studentDropped(Object student) {
		if(students.contains(student)) {
			students.remove(student);
			if(waitingList.size()>0)
				enrollFromWaitingList();
			if(isSeatAvailable() && getState()!=SeminarState.Full)
				seatAvailable();
		} else if(waitingList.contains(student)) { 
			waitingList.remove(student);
		}
	}
	
	@Event(@Transition(from="Full", to="OpenForEnrollement"))
	private void seatAvailable() {
	}

	@Event(@Transition(from="ClosedToEnrollement", to="BeingTaught"))
	public void termStarted() {
		
	}
	
	@Event(@Transition(from="BeingTaught", to="FinalExams"))
	public void classesEnd() {
		
	}
	
	private void enrollFromWaitingList() {
		if(waitingList.size()>0) {
			Object student = waitingList.get(0);
			students.add(student);
			waitingList.remove(0);
		}
	}

	private void addStudent( Object student ) {
		students.add(student);
	}
	
	
	private void addToWaitingList( Object student ) {
		waitingList.add(student);
	}

	@Event( {
			@Transition(from={"OpenForEnrollement","Full"}, to="ClosedToEnrollement"),
			@Transition(from="FinalExams", to="End")
		})
	public void closed( ) {
	}


	// Implementation

	@OnStateEntry("OpenForEnrollement")
	private void logSize() {
		System.out.println("We have " + students.size() + " students.");
	}

	@OnStateEntry("ClosedToEnrollement")
	private void notifyInstructor() {
		System.out.println("Dear instructor seminar " + getName() + " is closed, and is going to be in " + getWhen() + " at " + getWhere() + " and have " + getSeminarSize() + " students.");
	}

}
